using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Input;

namespace Gremlin.Collision
{
    struct TPositioning
    {
        public Vector3 position;
        public Quaternion rotation;
        public Vector3 deltaPos;
        public Quaternion deltaRot;
        public Vector3 scale;
        public float weight;
        public float frictionFactor;
    }

    class TestCollision
    {
        private CollisionObject _co;
        private TPositioning _data;
        private Model _model;
        private Matrix projection, view;

        public TestCollision(TPositioning tpos, ref CollisionHull ch, Microsoft.Xna.Framework.Game game)
        {
            float aspectRatio = game.GraphicsDevice.Viewport.Width / game.GraphicsDevice.Viewport.Height;
            projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
                aspectRatio, 1.0f, 10000.0f);

            view = Matrix.CreateLookAt(new Vector3(0.0f, 500.0f, -500.0f), Vector3.Zero, Vector3.Up);

            _co = new CollisionObject(ref ch);
            _data = tpos;
            _co.SetSteadyStates(_data.scale, _data.weight, _data.frictionFactor);
            _model = game.Content.Load<Model>("boxes2");
        }

        public CollisionObject GetCollisionObject()
        {
            return _co;
        }
        public CollisionObject UpdateCollisionState(CollisionState collisionState, GameTime gameTime)
        {
            //Can do check for gameTime, if equal, no need for update
            if (collisionState == CollisionState.PRE_COLLISION)
                return SetPreCollisionData();
            else
                return SetPostCollisionData();
        }
        public CollisionObject SetPreCollisionData()
        {
            _co.Set(_data.position, _data.rotation, _data.deltaPos, _data.deltaRot, 1);
            return _co;
        }
        public CollisionObject SetPostCollisionData()
        {
            float iForce, rayDamage;
            _co.Get(out _data.deltaPos, out _data.deltaRot, out iForce, out rayDamage);
            return _co;
        }
        public void Update()
        {
            _data.position += _data.deltaPos;
            _data.rotation = Quaternion.Concatenate(_data.rotation, _data.deltaRot);
        }
        public void Draw()
        {
            Matrix matTrans = Matrix.CreateTranslation(_data.position);
            Matrix matRotat = Matrix.CreateFromQuaternion(_data.rotation);
            Matrix matScale = Matrix.CreateScale(_data.scale);
            Matrix world = matScale * matRotat * matTrans;
            DrawModel(_model, world);
        }
        private void DrawModel(Model m, Matrix world)
        {
            Matrix[] transforms = new Matrix[m.Bones.Count];
            m.CopyAbsoluteBoneTransformsTo(transforms);

            foreach (ModelMesh mesh in m.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();

                    effect.View = view;
                    effect.Projection = projection;
                    effect.World = transforms[mesh.ParentBone.Index] * world;
                }
                mesh.Draw();
            }
        }
    }

    public class TestScene : Microsoft.Xna.Framework.DrawableGameComponent
    {
        private TestCollision _tc0, _tc1, _tc2;
        private CollisionHull _ch0;
        private CollisionHandler _handler;
        private Microsoft.Xna.Framework.Game _game;
        private Ray ray;

        public TestScene(Microsoft.Xna.Framework.Game game)
            : base(game)
        {
            _game = game;
        }

        public override void Initialize()
        {

            _ch0 = new CollisionHull(_game.Content.Load<List<Dictionary<string, object>>>("boxes"));
            TPositioning data;

            data.position = new Vector3(0, 200, 50);
            data.rotation = Quaternion.CreateFromAxisAngle(Vector3.Up, -MathHelper.PiOver4);
            data.deltaRot = Quaternion.CreateFromAxisAngle(Vector3.Up, 0);
            data.scale = new Vector3(1, 1.5f, 0.4f);
            data.deltaPos = new Vector3(0.2f, -1f, 0);
            data.weight = 1;
            data.frictionFactor = 0.75f;
            _tc0 = new TestCollision(data, ref _ch0, _game);

            data.position = new Vector3(-200, 0, 0);
            data.rotation = Quaternion.CreateFromAxisAngle(Vector3.Up, MathHelper.PiOver4);
            data.rotation = Quaternion.CreateFromAxisAngle(Vector3.Up, 0);
            data.deltaRot = Quaternion.CreateFromAxisAngle(Vector3.Up, 0);
            data.scale = new Vector3(0.75f ,1 , 1.3f);
            data.deltaPos = new Vector3(1.1f, 0, 0);
            data.weight = 1;
            data.frictionFactor = 0.75f;
            _tc1 = new TestCollision(data, ref _ch0, _game);

            data.position = new Vector3(200, 100, 0);
            data.rotation = Quaternion.CreateFromAxisAngle(Vector3.Up, 0);
            data.deltaRot = Quaternion.CreateFromAxisAngle(Vector3.Backward, 0.0001f);
            data.scale = new Vector3(0.5f, 0.5f, 1f);
            data.deltaPos = new Vector3(-0.7f, 0, 0);
            data.weight = 1;
            data.frictionFactor = 0.75f;
            _tc2 = new TestCollision(data, ref _ch0, _game);

            _handler = new CollisionHandler();
            _handler.CollisionEvent += _tc0.UpdateCollisionState;
            _handler.CollisionEvent += _tc1.UpdateCollisionState;
            _handler.CollisionEvent += _tc2.UpdateCollisionState;

            base.Initialize();
        }

        public Ray CalculateCursorRay(Matrix projectionMatrix, Matrix viewMatrix, Vector2 Position)
        {
            // create 2 positions in screenspace using the cursor position. 0 is as
            // close as possible to the camera, 1 is as far away as possible.
            Vector3 nearSource = new Vector3(Position, 0f);
            Vector3 farSource = new Vector3(Position, 1f);

            // use Viewport.Unproject to tell what those two screen space positions
            // would be in world space. we'll need the projection matrix and view
            // matrix, which we have saved as member variables. We also need a world
            // matrix, which can just be identity.
            Vector3 nearPoint = GraphicsDevice.Viewport.Unproject(nearSource,
                projectionMatrix, viewMatrix, Matrix.Identity);

            Vector3 farPoint = GraphicsDevice.Viewport.Unproject(farSource,
                projectionMatrix, viewMatrix, Matrix.Identity);

            // find the direction vector that goes from the nearPoint to the farPoint
            // and normalize it....
            Vector3 direction = farPoint - nearPoint;
            direction.Normalize();

            // and then create a new ray using nearPoint as the source.
            return new Ray(nearPoint, direction);
        }

        public void ImpactResponce(CollisionObject collisionObject, Vector3 point, Vector3 delta)
        {
            collisionObject.ImpulsForce(point, delta, 2);
        }

        public override void Update(GameTime gameTime)
        {
            float aspectRatio = _game.GraphicsDevice.Viewport.Width / _game.GraphicsDevice.Viewport.Height;
            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
                aspectRatio, 1.0f, 10000.0f);

            Matrix view = Matrix.CreateLookAt(new Vector3(0.0f, 500.0f, -500.0f), Vector3.Zero, Vector3.Up);
            Game.IsMouseVisible = true;
            ray = CalculateCursorRay(projection, view, new Vector2(Mouse.GetState().X, Mouse.GetState().Y));
            if (Mouse.GetState().LeftButton == ButtonState.Pressed)
            {
                Vector3 point;
                _handler.RayIntersection(ray.Position, ray.Direction*1000, gameTime, ImpactResponce, out point);
            }

            _handler.DoCollision(gameTime);
            _tc0.Update();
            _tc1.Update();
            _tc2.Update();

            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            _game.GraphicsDevice.RenderState.DepthBufferEnable = true;
            _tc0.Draw();
            _tc1.Draw();
            _tc2.Draw();
        }

    }
}
